package cyou.breathe.blog.user.domain.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.CacheManager;
import com.alicp.jetcache.anno.CacheInvalidate;
import com.alicp.jetcache.anno.CacheRefresh;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.alicp.jetcache.template.QuickConfig;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cyou.breathe.blog.api.user.constant.UserStateEnum;
import cyou.breathe.blog.api.user.request.UserModifyRequest;
import cyou.breathe.blog.api.user.response.UserOperatorResponse;
import cyou.breathe.blog.api.user.response.data.InviteRankInfo;
import cyou.breathe.blog.base.exception.BizException;
import cyou.breathe.blog.base.exception.RepoErrorCode;
import cyou.breathe.blog.base.response.PageResponse;
import cyou.breathe.blog.lock.DistributeLock;
import cyou.breathe.blog.user.domain.entity.User;
import cyou.breathe.blog.user.infrastructure.exception.UserErrorCode;
import cyou.breathe.blog.user.infrastructure.exception.UserException;
import cyou.breathe.blog.user.infrastructure.mapper.UserMapper;
import jakarta.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RScoredSortedSet;
import org.redisson.api.RedissonClient;
import org.redisson.client.protocol.ScoredEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static cyou.breathe.blog.user.infrastructure.exception.UserErrorCode.*;

/**
 * @author: breathe
 * @createTime: 2025-08-27
 */
@Service
public class UserService extends ServiceImpl<UserMapper, User> {

    private static final String DEFAULT_NICK_NAME_PREFIX = "用户_";

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private CacheManager cacheManager;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private UserCacheDelayDeleteService userCacheDelayDeleteService;

    /**
     * 邀请排行榜
     */
    private RScoredSortedSet<String> inviteRank;

    /**
     * 通过用户ID对用户信息做的缓存
     */
    private Cache<String, User> idUserCache;

    @PostConstruct
    public void init() {
        QuickConfig idQc = QuickConfig.newBuilder(":user:cache:id:")
                .cacheType(CacheType.BOTH)
                .expire(Duration.ofHours(2))
                .syncLocal(true)
                .build();
        idUserCache = cacheManager.getOrCreateCache(idQc);
    }

    /**
     * 注册
     * @param number 号码
     * @param inviteCode 邀请码
     * @param password 密码
     * @return
     */
    @DistributeLock(keyExpression = "#number", scene = "USER_REGISTER")
    @Transactional(rollbackFor = Exception.class)
    public UserOperatorResponse register(String number, String inviteCode, String password) {
        //用户名
        String defaultNickName;
        String randomString;

        do {
            randomString = RandomUtil.randomString(6).toUpperCase();
            defaultNickName = DEFAULT_NICK_NAME_PREFIX + randomString;
        } while (nickNameExist(defaultNickName) || inviteCodeExist(randomString));

        //邀请人
        String inviterId = null;
        if (StringUtils.isNotBlank(inviteCode)) {
            User inviter = userMapper.findByInviteCode(inviteCode);
            if (inviter != null) {
                inviterId = inviter.getId().toString();
            }
        }

        //充血模型
        User user = register(number, defaultNickName, password, randomString, inviterId);
        Assert.notNull(user, UserErrorCode.USER_OPERATE_FAILED.getCode());

        updateInviteRank(inviterId);

        updateUserCache(user.getId().toString(), user);

        UserOperatorResponse userOperatorResponse = new UserOperatorResponse();
        userOperatorResponse.setSuccess(true);

        return userOperatorResponse;
    }

    /**
     * 冻结
     * @param userId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public UserOperatorResponse freeze(Long userId) {
        UserOperatorResponse userOperatorResponse = new UserOperatorResponse();
        User user = userMapper.findById(userId);

        Assert.notNull(user, () -> new UserException(USER_NOT_EXIST));
        Assert.isTrue(user.getState() == UserStateEnum.ACTIVE, () -> new UserException(USER_STATE_IS_NOT_ACTIVE));

        idUserCache.remove(user.getId().toString());

        if (user.getState() == UserStateEnum.FROZEN) {
            userOperatorResponse.setSuccess(true);
            return userOperatorResponse;
        }

        user.setState(UserStateEnum.FROZEN);
        boolean updateById = updateById(user);
        Assert.isTrue(updateById, () -> new BizException(RepoErrorCode.UPDATE_FAILED));

        userCacheDelayDeleteService.delayCacheDelete(idUserCache, user);

        userOperatorResponse.setSuccess(true);

        return userOperatorResponse;
    }

    /**
     * 解冻
     * @param userId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public UserOperatorResponse unfreeze(Long userId) {
        UserOperatorResponse userOperatorResponse = new UserOperatorResponse();

        User user = userMapper.findById(userId);
        Assert.notNull(user, () -> new UserException(USER_NOT_EXIST));

        idUserCache.remove(user.getId().toString());

        if (user.getState() == UserStateEnum.ACTIVE) {
            userOperatorResponse.setSuccess(true);
            return userOperatorResponse;
        }

        user.setState(UserStateEnum.ACTIVE);
        boolean updateResult = updateById(user);

        Assert.isTrue(updateResult, () -> new BizException(RepoErrorCode.UPDATE_FAILED));

        userCacheDelayDeleteService.delayCacheDelete(idUserCache, user);
        userOperatorResponse.setSuccess(true);
        return userOperatorResponse;
    }

    /**
     * 分页查询用户接口
     * @param keyword
     * @param state
     * @param currentPage
     * @param pageSize
     * @return
     */
    public PageResponse<User> pageQueryByState(String keyword, String state, int currentPage, int pageSize) {
        Page<User> page = new Page<>(currentPage, pageSize);
        LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(User.class)
                .eq(User::getState, state)
                .like(keyword != null && Validator.isEmail(keyword), User::getEmail, keyword)
                .like(keyword != null && Validator.isMobile(keyword), User::getTelephone, keyword)
                .orderBy(true, true, User::getGmtCreate);

        Page<User> userPage = this.page(page, queryWrapper);

        return PageResponse.of(userPage.getRecords(), (int) userPage.getTotal(), pageSize, currentPage);
    }

    /**
     * 修改用户信息
     * @param userModifyRequest
     * @return
     */
    @CacheInvalidate(name = ":user:cache:id", key = "#userModifyRequest.userId")
    @Transactional(rollbackFor = Exception.class)
    public UserOperatorResponse modify(UserModifyRequest userModifyRequest) {
        UserOperatorResponse userOperatorResponse = new UserOperatorResponse();

        User user = userMapper.findById(userModifyRequest.getUserId());
        Assert.notNull(user, () -> new UserException(USER_NOT_EXIST));
        Assert.isTrue(user.canModifyInfo(), () -> new UserException(USER_STATUS_CANT_OPERATE));

        if (StringUtils.isNotBlank(userModifyRequest.getNickName()) && nickNameExist(userModifyRequest.getNickName())) {
            throw new UserException(NICK_NAME_EXIST);
        }

        BeanUtil.copyProperties(userModifyRequest, user);

        if (StringUtils.isNotBlank(userModifyRequest.getPassword())) {
            user.setPassword(DigestUtil.md5Hex(userModifyRequest.getPassword()));
        }

        if (updateById(user)) {
            userOperatorResponse.setSuccess(true);
            return userOperatorResponse;
        }

        userOperatorResponse.setSuccess(false);
        userOperatorResponse.setErrorCode(USER_OPERATE_FAILED);

        return userOperatorResponse;
    }

    /**
     * 获取用户排名
     * @param userId
     * @return
     */
    public Integer getInviteRank(String userId) {
        Integer rank = inviteRank.revRank(userId);

        if (rank != null) {
            return rank + 1;
        }
        return null;
    }

    /**
     * 获取用户邀请列表
     * @param inviterId
     * @param currentPage
     * @param pageSize
     * @return
     */
    public PageResponse<User> getUsersByInviterId(String inviterId, int currentPage, int pageSize) {
        Page<User> page = new Page<>(currentPage, pageSize);

        LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(User.class)
                .select(User::getNickName, User::getGmtCreate)
                .eq(User::getInviterId, inviterId)
                .orderByDesc(true, User::getGmtCreate);

        Page<User> userPage = this.page(page, queryWrapper);

        return PageResponse.of(userPage.getRecords(), (int)userPage.getTotal(), pageSize, currentPage);
    }

    /**
     *获取排行榜前N个用户
     * @param topN
     * @return
     */
    public List<InviteRankInfo> getTopN(Integer topN) {
        Collection<ScoredEntry<String>> rankInfos = inviteRank.entryRangeReversed(0, topN - 1);

        List<InviteRankInfo> inviteRankInfos = new ArrayList<>();

        if (rankInfos != null) {
            for (ScoredEntry<String> rankInfo : rankInfos) {
                InviteRankInfo inviteRankInfo = new InviteRankInfo();
                String userId = rankInfo.getValue();
                if (StringUtils.isNotBlank(userId)) {
                    User user = findById(Long.valueOf(userId));
                    if (user != null) {
                        inviteRankInfo.setNickName(user.getNickName());
                        inviteRankInfo.setInviteCode(user.getInviteCode());
                        inviteRankInfo.setInviteScore(rankInfo.getScore().intValue());
                        inviteRankInfos.add(inviteRankInfo);
                    }
                }
            }
        }

        return inviteRankInfos;
    }

    private void updateUserCache(String userId, User user) {
        idUserCache.put(userId, user);
    }

    /**
     * 更新排名，排名规则：
     * <pre>
     *     1、优先按照分数排，分数越大的，排名越靠前
     *     2、分数相同，则按照上榜时间排，上榜越早的排名越靠前
     * </pre>
     *
     * @param inviterId
     */
    private void updateInviteRank(String inviterId) {
        if (inviterId == null) {
            return ;
        }

        RLock rLock = redissonClient.getLock(inviterId);

        rLock.lock();

        try {
            Double score = inviteRank.getScore(inviterId);

            if (score == null) {
                score = 0.0;
            }

            //获取最近一次上榜时间
            long currentTimeStamp = System.currentTimeMillis();
            //把上榜时间转成小数(时间戳13位，所以除以10000000000000能转成小数)，并且倒序排列（用1减），即上榜时间越早，分数越大（时间越晚，时间戳越大，用1减一下，就反过来了）
            double timePartScore = 1 - (double) currentTimeStamp / 10000000000000L;

            inviteRank.add(score.intValue() + 100 + timePartScore, inviterId);
        } finally {
            rLock.unlock();
        }
    }

    public boolean nickNameExist(String nickName) {
        return userMapper.findByNickName(nickName) != null;
    }

    public boolean inviteCodeExist(String inviteCode) {
        return userMapper.findByInviteCode(inviteCode) != null;
    }

    /**
     * 注册
     *
     * @param number
     * @param nickName
     * @param password
     * @return
     */
    private User register(String number, String nickName, String password, String inviteCode, String inviterId) {
        if (Validator.isMobile(number)) {
            if (userMapper.findByTelephone(number) != null) {
                throw new UserException(DUPLICATE_TELEPHONE_NUMBER);
            }
        } else if (Validator.isEmail(number)) {
            if (userMapper.findByEmail(number) != null) {
                throw new UserException(DUPLICATE_EMAIL_NUMBER);
            }
        } else {
            throw new UserException(USER_REGISTER_PARAM_ERROR);
        }

        User user = new User();
        user.register(number, nickName, password, inviteCode, inviterId);
        return save(user) ? user : null;
    }

    /**
     * 通过用户ID查询用户信息
     * @param userId 用户ID
     * @return 用户信息
     */
    @Cached(name = ":user:cache:id:", cacheType = CacheType.BOTH, key = "#userId", cacheNullValue = true)
    @CacheRefresh(refresh = 60, timeUnit = TimeUnit.MINUTES)
    public User findById(Long userId) {
        return userMapper.findById(userId);
    }

    public User findByTelephone(String telephone) {
        return userMapper.findByTelephone(telephone);
    }

    public User findByTelephoneOrEmail(String number) {
        if (Validator.isMobile(number)) {
            return findByTelephone(number);
        } else if (Validator.isEmail(number)) {
            return findByEmail(number);
        }
        return null;
    }

    public User findByPhoneAndPassword(String phone, String password) {
        return userMapper.findByTelephoneAndPassword(phone, password);
    }

    public User findByEmail(String email) {
        return userMapper.findByEmail(email);
    }

    public User findByEmailAndPassword(String email, String password) {
        return userMapper.findByEmailAndPassword(email, password);
    }
}
